package minidb.file;

import static minidb.type.Const.*;
import minidb.buffer.Buffer;

public class FileMgr {	
	java.io.RandomAccessFile dbFile ;
	private java.nio.ByteBuffer freePage;
	private byte[] lock = new byte[0];

	public FileMgr(String Filename){
		try{
			dbFile = new java.io.RandomAccessFile(Filename,"rws");
		}catch(java.io.FileNotFoundException e){
			throw new RuntimeException(e.getMessage());
		}
		freePage = java.nio.ByteBuffer.allocateDirect(PAGE_SIZE);
		if(size() > 0){
			read(1,freePage);
			while(freePage.getInt(0)!=0)
				read(freePage.getInt(0),freePage);
			freePage.position(12);
			while(freePage.getInt()!=0) ;
			freePage.position(freePage.position()-4);				
		}
		else{
			java.nio.ByteBuffer tab = java.nio.ByteBuffer.allocateDirect(PAGE_SIZE);
			tab.clear();
			tab.rewind();
			tab.putInt(0,0);
			tab.putInt(4,0);
			tab.putInt(8,0);
			tab.putInt(12,16);
			freePage.rewind();
			freePage.putInt(0,0);
			freePage.putInt(4,1);
			freePage.putInt(8,0);
			write(0,tab);
			write(1,freePage);
			tab.clear();
			tab.putInt(CURRENT_PAGE, 2);
			tab.putInt(FREE, 16);
			write(2,tab);
			freePage.position(12);
		}	
	}

	public synchronized void read(int blk, java.nio.ByteBuffer bb) {
		try {
			bb.clear();
			java.nio.channels.FileChannel fc = dbFile.getChannel();
			fc.read(bb, blk * PAGE_SIZE);
		}
		catch (java.io.IOException e) {
			throw new RuntimeException("cannot read block " + blk + "ERROR: " + e.getMessage());
		}
	}
	
	public synchronized void write(int blk, java.nio.ByteBuffer bb) {
		try {
			bb.rewind();
			java.nio.channels.FileChannel fc = dbFile.getChannel();
			fc.write(bb, blk * PAGE_SIZE);
		}
		catch (java.io.IOException e) {
			throw new RuntimeException("cannot write block:\t" + blk + "\tERROR: " + e.toString());
		}
	}
	
	public void deallocPage(int blk){
		synchronized(lock){
			if(blk > size())
				throw new RuntimeException(blk + " is invalid");
			java.nio.ByteBuffer tmp = java.nio.ByteBuffer.allocateDirect(PAGE_SIZE);
			tmp.clear();
			tmp.putInt(CURRENT_PAGE,blk);
			Buffer buf = bufpool.pin(blk);
			buf.putByte(0, tmp, PAGE_SIZE);
			bufpool.unpin(buf);
			if(freePage.position() <= (PAGE_SIZE -4)){
				freePage.putInt(blk);
			}
			else{
				int prevPage;
				prevPage = freePage.getInt(4);
				freePage.position(0);
				freePage.putInt(blk);
				write(prevPage, freePage);
				freePage.clear();
				freePage.position(0);
				freePage.putInt(0);
				freePage.putInt(blk);
				freePage.putInt(prevPage);
			}
		}
	}

	public synchronized void close(){
		freePage.rewind();
		write(freePage.getInt(4),freePage);
		bufpool.flushAll();
	}
	
	public int allocPage() {
		synchronized(lock){
			int newblknum;
			if(freePage.position() == 12){
				newblknum = freePage.getInt(4);
				int prevPage = freePage.getInt(8);
				if(prevPage == 0 ){
					newblknum = size();
				}
				else{
					read(prevPage,freePage);
					freePage.rewind();
					freePage.putInt(0);
					freePage.position(1024);
				}
			}
			else{
				newblknum = freePage.getInt(freePage.position()-4);
				freePage.putInt(freePage.position()-4,0);
				freePage.position(freePage.position()-4);
			}
			java.nio.ByteBuffer tmp = java.nio.ByteBuffer.allocateDirect(PAGE_SIZE);
			tmp.clear();
			tmp.putInt(CURRENT_PAGE,newblknum);
			tmp.rewind();
			write(newblknum, tmp);
			return newblknum;
		}
	}
	
	public synchronized int size() {
		try {
			java.nio.channels.FileChannel fc = dbFile.getChannel();
			int size = (int)fc.size()/ PAGE_SIZE;
			return size;
		}
		catch (java.io.IOException e) {
			throw new RuntimeException("cannot access ");
		}
	}
}
